/*
 * Copyright the original author or authors.
 * 
 * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.mozilla.org/MPL/MPL-1.1.html
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.bourre.commands.Command;
import com.bourre.commands.Delegate;
import com.bourre.data.collections.Map;
import com.bourre.data.iterator.Iterator;
import com.bourre.log.PixlibDebug;
import com.kairos.motion.AdvancedMovieClipRT;
import com.bourre.structures.Range;

/**
 * {@code FrameControllerRT} allow to assign a {@code Command} object 
 * to a {@code MovieClip} frame. 
 * 
 * <p>When frames are skipped because the time between calls is greater
 * than the frames duration the {@code FrameControllerRT} will simulate
 * the move of the play head on those frames. Results that all registered
 * commands continue to be executed even if the frames they register with
 * are skipped.</p>
 * 
 * <p>As all frames are played (virtually if needed), commands witch will
 * modify the {@code FrameControllerRT} are effective immediatly, even if
 * they are on virtually played frames.</p>
 * 
 * <p> In the example below the framerate of the animation is set to 20fps, the
 * {@code FrameControllerRT} object have a framerate 4 times greater, 
 * then each calls represent 4 frames (in average).</p>
 * 
 * <code>
 * var fc : FrameControllerRT = new FrameControllerRT ( mcTarget, 80 );
 * fc.addStop ( 17 );
 * fc.addProxy ( 18, _global, trace, "command never called" );
 * fc.play();
 * </code>
 * 
 * @author 	Cédric Néhémie
 * @see		com.kairos.motion.AdvancedMovieClipRT
 */
class com.kairos.commands.FrameControllerRT extends AdvancedMovieClipRT 
{	
	private var _a : Map;
	
	/**
	 * The {@code FrameControllerRT} work as the {@code AdvancedMovieClipRT},
	 * so the constructor don't add or remove anything.
	 * 
	 * @param   mc	The MovieClip to work with.    
	 * @param   fps The speed of the animation.
	 * @param	loopCount Number of loops before stop.
	 * @param	range A valid {@code Range} object
	 * @param	r	{@code true} to make the movieclip playing reversed. 
	 */
	public function FrameControllerRT( 	mc : MovieClip, 
										fps : Number, 
										loopCount : Number, 
										range : Range, 
										r : Boolean ) 
	{
		super( mc, fps, loopCount, range, r );
		
		_a = new Map();
	}
	
	/**
	 * Add a command to a specific frame of the animation.
	 * 
	 * <p>If the command don't exist or if the frame can't
	 * be found in the object, the function failed and return
	 * {@code false}.</p>
	 * 
	 * <p>If a command allready exist in the map, the function
	 * failed and return {@code false}.</p>
	 * 
	 * @param	f	Frame index witch will trigger the command.
	 * @param	o	{@code Command} to execute.
	 * @return	{@code true} if the command have been registered,
	 * 			either {@code false}
	 */
	public function addCommand ( f : Number, o : Command ) : Boolean
	{
		if( !isValidFrame ( f ) || !isEmpty( f ) || !o )
		{
			PixlibDebug.ERROR( this + ".addCommand() can't add " + o +" at " + f );
			return false;
		}
			
		_a.put( f, o );		
		return true;
	}
	
	/**
	 * Add a function proxy to a specific frame of the animation.
	 * 
	 * <p>If the proxy don't exist or if the frame can't
	 * be found in the object, the function failed and return
	 * {@code false}.</p>
	 * 
	 * <p>If a command allready exist in the map, the function
	 * failed and return {@code false}.</p>
	 * 
	 * <p>You can pass additionnal arguments to the proxy as
	 * in Pixlib implementation.</p>
	 * 
	 * @param	f	Frame index witch will trigger the proxy.
	 * @param	o	Scope object.
	 * @param	fn	Function to proxy.
	 * @return	{@code true} if the proxy have been registered,
	 * 			either {@code false}
	 */
	public function addProxy ( f : Number, o, fn : Function ) : Boolean
	{
		if( !isValidFrame ( f ) || !isEmpty( f ) || !o || !fn ) 
		{
			PixlibDebug.ERROR( this + ".addProxy() can't add the proxy at " + f );
			return false;
		}
			
		var d : Delegate = new Delegate ( o, fn );
		d.setArguments.apply( d, arguments.splice(3) );
			
		_a.put ( f, d );		
		return true;
	}
	
	/**
	 * Add a simple {@code stop} call at the specified frame.
	 * 
	 * <p>{@code addStop} method is subject at all restrictions
	 * of the {@link addCommand} and {@link addProxy}.</p>
	 * 
	 * @param	f	Frame where the animation stop.
	 * @return	{@code true} if the stop have been registered,
	 * 			either {@code false}
	 */
	public function addStop ( f : Number ) : Boolean
	{
		if( !isValidFrame ( f ) || !isEmpty( f ) ) 
		{
			PixlibDebug.ERROR( this + ".addStop() can't add the command at " + f );
			return false;
		}
			
		_a.put ( f, new Delegate( this, stop ) );		
		return true;
	}
	
	/**
	 * Remove the passed-in command if finded in the map.
	 * 
	 * <p>The function return {@code true} if the command
	 * have been removed, either {@code false}.
	 * 
	 * @param	o	{@code Command} to remove.
	 * @return	{@code true} if the command have been removed,
	 * 			either {@code false}
	 */
	public function remove ( o : Command ) : Boolean
	{
		if( !_a.containsValue( o ) ) 
		{
			PixlibDebug.WARN( o + " don't exist in " + this );
			return false;
		}
		
		var i : Iterator = _a.getValuesIterator();
		while( i.hasNext() )
		{
			var oc = i.next();
			if( oc === o )
			{
				_a.remove( i.getIndex() );
				break;
			} 
		}
		return true;
	}
	
	/**
	 * Remove a {@code Command} registered to the specified
	 * frame.
	 * 
	 * <p>If the passed-in frame don't exist in the animation
	 * or if there's no command registered with that frame, 
	 * the function failed and return {@code false}.</p>
	 * 
	 * @param	f	Frame number in the current {@code MovieClip}.
	 * @return	{@code true} if a command have been removed,
	 * 			either {@code false}
	 */
	public function removeAt ( f : Number ) : Boolean
	{
		if( !isValidFrame ( f ) ) 
			return false;

		_a.remove( f );
		return true;
	}
	
	/**
	 * Returns command stored at the specified frame.
	 * 
	 * <p>If the passed-in frame don't exist in the animation
	 * or if there's no command stored at this framethe function
	 * return {@code null}.</p>
	 * 
	 * @param	f	Frame number in the current {@code MovieClip}.
	 * @return	{@code Comand} stored at the specified frame or {@code null}
	 */
	public function getCommand ( f : Number ) : Command
	{
		if( !isValidFrame ( f ) ) 
			return null;
			
		return _a.get( f );
	}
	
	/**
	 * Returns {@code true} if the frame exist in the current
	 * {@code MovieClip}.
	 * 
	 * @param	f	Frame index to verify.
	 * @return	{@code true} if the frame is valid, either {@code false}.
	 */
	public function isValidFrame ( f : Number ) : Boolean
	{
		return ( f > 0 && f <= _m._totalFrames );
	} 
	
	/**
	 * Returns true if there is no commands stored at the
	 * specified frame.
	 * 
	 * @param	f	Frame index to verify.
	 * @return	{@code true} if the frame is empty, either {@code false}.
	 */
	public function isEmpty ( f : Number ) : Boolean
	{
		return !_a.containsKey( f );
	}
	
	/**
	 * Override the {@code AdvancedMovieClipRT._movePlayHead} method.
	 * 
	 * That's here we are processing commands.
	 */	 
	private function _movePlayHead ( f : Number ) : Void
	{	
		var sf : Number = _f;
		/*
		 * If the step is greater than 1 frame we loop
		 * for each step and trigger command. If a step
		 * stop the animation the loop break.
		 */
	 	if( f > 1 )
		{
			var n : Number = 0;
			while( ++n <= f && _bIP )
			{
				super._movePlayHead ( 1 );
			}
		}
		else
		{
			super._movePlayHead ( f );
		}
	 	super._frameChanged ( _nRangeStart + _f );	
	}
	/**
	 * Called when frame changed according to internal computation.
	 */
	private function _frameChanged ( f : Number ) : Void
	{ 
	 	if( !isEmpty( f ) )
		{
			_a.get( f ).execute();
		}
	}
}